Kit refresh: Expo SDK 55, latest deps, fixes of native flows#13
Conversation
- expo sdk v55 - few commands added for dep management - fix android simulator supabase connection - fix node types issues
Adds components.json + update to latest RNR kit components. Misc: subtasks - doc: comment on kit overwrites (keeping custom styles) - fix: dark mode breakeges - fix: use button link correctly - fix: state errors on account page - fix: let TS handle no undef instead of eslint - fix: theme toggling state - fix: render validation errors correctly
- non native skeleton - missing package declaration - optimistic profile picture set - toaster with correct theme support
WalkthroughRefactors the UI kit to function components with shared primitives and updated styling, updates account/auth forms and hooks (including avatar flow), adjusts app screens/configs, rewrites Supabase helper types and Android client URL handling, adds a Supabase seed, and updates workspace/tooling configurations. ChangesUI Kit overhaul and new primitives
Account/auth flows and app UI/config updates
Supabase types/client and workspace/tooling updates
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/features/auth/src/password-reset/password-reset-form.tsx (1)
43-50:⚠️ Potential issue | 🟠 Major | ⚡ Quick winWait for mutation success before showing success toast.
Line 49 shows
toast.success(...)executing immediately aftermutate(...)rather than waiting for the request to complete. If the password reset request fails, users will still see the success message because it displays before the mutation result is known.Move the success toast into an
onSuccesscallback passed to themutate()call. The hook itself (useRequestResetPassword) defines no success/error handlers, so notification logic belongs in the component calling the mutation.Suggested fix
- onPress={form.handleSubmit((data) => { - passwordResetMutation.mutate({ - email: data.email, - redirectTo: '/', - }); - - toast.success('We sent you an email to reset your password.'); - }, onValidationErrors((message) => toast.error(message)))} + onPress={form.handleSubmit((data) => { + passwordResetMutation.mutate( + { + email: data.email, + redirectTo: '/', + }, + { + onSuccess: () => { + toast.success('We sent you an email to reset your password.'); + }, + onError: () => { + toast.error('Failed to send reset email. Please try again.'); + }, + }, + ); + }, onValidationErrors((message) => toast.error(message)))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/features/auth/src/password-reset/password-reset-form.tsx` around lines 43 - 50, The success toast is shown immediately after calling passwordResetMutation.mutate instead of waiting for the request to complete; update the form.handleSubmit handler so that mutate is called with an options object containing an onSuccess callback that calls toast.success('We sent you an email to reset your password.'), and keep validation errors routed through onValidationErrors as before; reference the passwordResetMutation.mutate call in the password-reset-form component (which uses useRequestResetPassword), and optionally add an onError callback to the mutate options to show toast.error on failure.
🧹 Nitpick comments (8)
packages/ui/src/components/ui/alert-dialog.tsx (1)
37-41: 💤 Low valueRedundant fragment wrapper around children.
The
<>{children}</>wrapper is unnecessary sincechildrencan be passed directly toNativeOnlyAnimatedView.♻️ Suggested simplification
<NativeOnlyAnimatedView entering={FadeIn.duration(200).delay(50)} exiting={FadeOut.duration(150)}> - <>{children}</> + {children} </NativeOnlyAnimatedView>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/alert-dialog.tsx` around lines 37 - 41, The JSX wraps children in an unnecessary fragment inside NativeOnlyAnimatedView; update the render in the alert dialog component to pass children directly to NativeOnlyAnimatedView (remove the <> {children} </> wrapper) while keeping the existing entering and exiting props (FadeIn.duration(200).delay(50) and FadeOut.duration(150)) so only NativeOnlyAnimatedView, entering, exiting and children remain.packages/ui/src/components/ui/menubar.tsx (1)
55-62: ⚡ Quick winControlled/uncontrolled state precedence may be inverted.
The expression
value={value ?? valueProp}gives precedence to internal state over the prop. For controlled components, the prop should typically take precedence. If a consumer passesvalueprop, they expect it to control the component.Additionally, if
onValueChangePropis provided (controlled mode), the internalsetValueis bypassed viacloseMenu, but theonValueChangehandler still falls back tosetValuewhenonValueChangePropis undefined, which is correct. However, the initial render and state reading logic could be clearer.♻️ Suggested fix
<MenubarPrimitive.Root className={cn( 'bg-background border-border flex h-10 flex-row items-center gap-1 rounded-md border p-1 shadow-sm shadow-black/5 sm:h-9', className )} - value={value ?? valueProp} + value={valueProp ?? value} onValueChange={onValueChangeProp ?? setValue} {...props} />This ensures that when the consumer provides a
valueprop (controlled mode), it takes precedence over internal state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/menubar.tsx` around lines 55 - 62, The Menubar is giving internal state precedence over the controlled prop; change the component to prioritize the consumer's prop by using valueProp before internal value (i.e., pass value={valueProp ?? value} to MenubarPrimitive.Root), keep the existing onValueChange handler pattern (onValueChange={onValueChangeProp ?? setValue}), and ensure the internal state initializer for value reads from valueProp when present (initialize value state from valueProp as the default) so controlled mode consistently wins and initial render is correct; reference MenubarPrimitive.Root, valueProp, value, onValueChangeProp, setValue, and closeMenu when making the edits.packages/ui/src/components/ui/dialog.tsx (1)
40-44: 💤 Low valueConsider simplifying nested animation views.
Two
NativeOnlyAnimatedViewcomponents are nested with slightly different timing. This adds unnecessary view hierarchy. The inner view only adds a 50ms delay, which could be combined into a single animation view.♻️ Suggested simplification
<DialogPrimitive.Overlay className={cn(...)} {...props} asChild={Platform.OS !== 'web'}> - <NativeOnlyAnimatedView entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)}> - <NativeOnlyAnimatedView entering={FadeIn.delay(50)} exiting={FadeOut.duration(150)}> - <>{children}</> - </NativeOnlyAnimatedView> - </NativeOnlyAnimatedView> + <NativeOnlyAnimatedView + entering={FadeIn.duration(200).delay(50)} + exiting={FadeOut.duration(150)}> + {children} + </NativeOnlyAnimatedView> </DialogPrimitive.Overlay>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/dialog.tsx` around lines 40 - 44, Replace the nested NativeOnlyAnimatedView pair with a single NativeOnlyAnimatedView that merges the timing (use FadeIn with the 50ms delay and 200ms duration and a single FadeOut.duration(150) for exiting) and render {children} directly; update the component that uses NativeOnlyAnimatedView (and references to FadeIn/FadeOut) to remove the extra wrapper and redundant fragment so the animation timings are combined in one view.packages/ui/src/components/ui/tooltip.tsx (1)
29-35: 💤 Low valueNative animation only handles top/bottom cases.
The entering animation logic only differentiates between
side === 'top'and other sides, while the web animation handles all four sides (bottom,left,right,top). If left/right tooltips are used on native, they'll get theFadeInUpanimation which may not be the ideal direction.If horizontal tooltips are not expected on mobile, this is fine as-is.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/tooltip.tsx` around lines 29 - 35, The entering animation only distinguishes side === 'top' vs other, causing left/right tooltips to use FadeInUp; update the NativeOnlyAnimatedView entering logic (the entering prop where side is checked) to handle 'left' and 'right' explicitly (e.g., map 'left' -> FadeInLeft, 'right' -> FadeInRight, 'bottom' -> FadeInUp, 'top' -> FadeInDown) so mobile matches the web behavior; modify the conditional/branch that currently uses FadeInDown and FadeInUp to a small switch or lookup that returns the correct FadeIn* animation for each side.packages/ui/src/components/ui/dropdown-menu.tsx (1)
106-114: 💤 Low valueInaccurate type assertion for overlay style.
The type assertion
overlayStyle as typeof StyleSheet.absoluteFillis not semantically correct.StyleSheet.absoluteFillreturns a registered style ID (number), not aViewStyleobject. WhileStyleSheet.flattenhandles both correctly at runtime, the type assertion is misleading.♻️ Suggested fix
native: overlayStyle ? StyleSheet.flatten([ StyleSheet.absoluteFill, - overlayStyle as typeof StyleSheet.absoluteFill, + overlayStyle, ]) : StyleSheet.absoluteFill,The
StyleProp<ViewStyle>type foroverlayStyleis already compatible withStyleSheet.flatten.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/dropdown-menu.tsx` around lines 106 - 114, The type assertion overlayStyle as typeof StyleSheet.absoluteFill is incorrect; remove that assertion and pass overlayStyle directly to StyleSheet.flatten (which accepts StyleProp<ViewStyle>), so modify the Platform.select branch used in the component where overlayStyle is passed (the StyleSheet.flatten call alongside StyleSheet.absoluteFill) to call StyleSheet.flatten([StyleSheet.absoluteFill, overlayStyle]) without the cast, ensuring overlayStyle retains its declared StyleProp<ViewStyle> type.packages/ui/src/components/ui/context-menu.tsx (1)
100-108: 💤 Low valueSame type assertion issue as dropdown-menu.
Same as in
dropdown-menu.tsx: the type assertionoverlayStyle as typeof StyleSheet.absoluteFillis not semantically accurate.♻️ Suggested fix
native: overlayStyle ? StyleSheet.flatten([ StyleSheet.absoluteFill, - overlayStyle as typeof StyleSheet.absoluteFill, + overlayStyle, ]) : StyleSheet.absoluteFill,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/context-menu.tsx` around lines 100 - 108, The code incorrectly asserts overlayStyle as typeof StyleSheet.absoluteFill; remove that inaccurate assertion and ensure overlayStyle is typed as a ViewStyle (or RegisteredStyle<ViewStyle>) instead. Update the ContextMenu/overlay block that uses Platform.select, StyleSheet.flatten and StyleSheet.absoluteFill so the flattened array is typed like [StyleSheet.absoluteFill, overlayStyle as ViewStyle] (or cast overlayStyle where it is defined to ViewStyle) rather than overlayStyle as typeof StyleSheet.absoluteFill; alternatively import ViewStyle from react-native and declare overlayStyle: ViewStyle | undefined to satisfy the type system.packages/ui/src/components/ui/card.tsx (1)
24-50: ⚡ Quick winIncorrect
RefAttributestype and inconsistent ref forwarding.
React.RefAttributes<typeof Text>should use the element type, nottypeof Text. In React 19,React.ComponentPropsalready includes ref, making the separateRefAttributesredundant.
CardTitleexplicitly destructures and forwardsref(line 26, 32), butCardDescriptiondoes not, leading to inconsistent behavior despite identical type signatures.Proposed fix
function CardTitle({ className, - ref, ...props -}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) { - +}: React.ComponentProps<typeof Text>) { return ( <Text - ref={ref} role="heading" aria-level={3} // Kit extension: text-card-foreground text-2xl tracking-tight className={cn( 'text-card-foreground text-2xl font-semibold leading-none tracking-tight', className, )} {...props} /> ); } function CardDescription({ className, ...props -}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) { +}: React.ComponentProps<typeof Text>) { return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/card.tsx` around lines 24 - 50, The CardTitle/CardDescription prop types use incorrect React.RefAttributes<typeof Text> and ref handling is inconsistent: remove the explicit RefAttributes usage and rely on React.ComponentProps<typeof Text> (which already includes ref in React 18+/19), and make both components forward refs consistently by converting them to React.forwardRef wrappers (forward the ref into the underlying Text element in both CardTitle and CardDescription); update function signatures to use React.forwardRef and ensure you forward the ref to the Text component (preserve className merging with cn and other props) so both components have correct types and consistent ref behavior.packages/ui/src/components/ui/button.tsx (1)
95-97: ⚡ Quick winIncorrect
RefAttributestype parameter.
React.RefAttributes<typeof Pressable>passes the component constructor type rather than the element/instance type. In React 19,React.ComponentPropsalready includes therefprop, so the additionalRefAttributesis redundant and incorrectly typed.Proposed fix
-type ButtonProps = React.ComponentProps<typeof Pressable> & - React.RefAttributes<typeof Pressable> & - VariantProps<typeof buttonVariants>; +type ButtonProps = React.ComponentProps<typeof Pressable> & + VariantProps<typeof buttonVariants>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/ui/button.tsx` around lines 95 - 97, The ButtonProps union incorrectly includes React.RefAttributes<typeof Pressable>, which uses the component constructor type and is redundant because React.ComponentProps<typeof Pressable> already includes the ref in React 19; remove the React.RefAttributes<typeof Pressable> part so ButtonProps becomes React.ComponentProps<typeof Pressable> & VariantProps<typeof buttonVariants>, or if an explicit ref type is required use React.RefAttributes<React.ComponentRef<typeof Pressable>> instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/expo-app/app/`(app)/auth/sign-up.tsx:
- Line 33: Update the CTA string in the Text element inside the sign-up
component so it matches the surrounding sentence-case copy style; replace
"Already have an Account? Sign In." with "Already have an account? Sign in." in
the JSX Text node (the line rendering the CTA in
apps/expo-app/app/(app)/auth/sign-up.tsx).
In `@apps/expo-app/components/settings/theme-mode-toggle.tsx`:
- Around line 20-24: The onValueChange handler for the theme toggle currently
awaits AsyncStorage.setItem without catching rejections; wrap the await call in
a try/catch inside the onValueChange async function (the handler that calls
setColorScheme and AsyncStorage.setItem) to handle storage failures, log a
non-sensitive error via your logger or console.error, and consider reverting or
ensuring UI/state consistency if setting storage fails (e.g., keep
setColorScheme but surface the error); do not swallow the error silently and
include identifiable context like the theme value and that the failure occurred
in AsyncStorage.setItem.
In `@packages/features/account/src/lib/hooks/use-update-avatar.ts`:
- Around line 38-40: The current flow deletes the previous avatar via
deleteAvatarObject before the new upload/update is committed, and
storage.remove() errors are silently ignored; change the flow in
use-update-avatar (the hook) to first upload the new avatar and commit the
user/profile update referencing the new URL, then only after a successful commit
call deleteAvatarObject for the old URL so the user never loses the current
avatar if upload/update fails, and modify deleteAvatarObject (and any direct
storage.remove() call) to catch and handle errors (log and surface/return them
instead of swallowing) so storage.remove failures are handled comprehensively.
- Line 100: The remove(...) call is currently ignoring storage errors; change it
to follow the same pattern as uploadAvatarBytes by awaiting
client.storage.from(AVATARS_BUCKET).remove([fileName]) and destructuring the
result into { data, error } (or similar), then throw or propagate the error if
present so deletions aren't silently swallowed; update the remove call inside
useUpdateAvatar (referencing AVATARS_BUCKET and the client.storage.remove
invocation) to handle and surface any storage errors.
In `@packages/ui/src/components/ui/accordion.tsx`:
- Around line 1-14: The file uses React types via React.ComponentProps (seen in
the props for components such as the AccordionTrigger, AccordionItem,
AccordionContent, and others where React.ComponentProps is referenced) but never
imports React; add an import for React (e.g., import * as React from 'react' or
import React from 'react') at the top of the file so the type references
resolve, keeping the existing imports intact and without changing runtime
behavior.
In `@packages/ui/src/components/ui/alert.tsx`:
- Around line 1-11: The type aliases AlertTitleProps, AlertDescriptionProps,
AlertIconProps and AlertActionsProps use React.ComponentProps but React isn't
imported as a namespace; modify the top import from 'react' to also import the
type ComponentProps (e.g., add "type ComponentProps" to the import list) and
change those aliases to use ComponentProps<...> instead of
React.ComponentProps<...> so the types resolve correctly.
In `@packages/ui/src/components/ui/badge.tsx`:
- Line 43: The destructive badge variant uses a fixed Tailwind class
'text-white' which breaks theme semantics; in the badge variant map (look for
the object or constant assigning destructive: 'text-white' inside the Badge
component or its variants in badge.tsx) replace that fixed class with the
semantic token 'text-destructive-foreground' (or the project’s equivalent
semantic foreground token) so the destructive badge uses theme-aware foreground
styling instead of a hardcoded white.
In `@packages/ui/src/components/ui/native-only-animated-view.tsx`:
- Around line 16-19: The web branch currently returns a Fragment and drops
wrapper props; change the Platform.OS === 'web' branch to render a View that
spreads the incoming props and wraps children so className, style, pointerEvents
and event handlers are preserved (i.e., replace the Fragment with a View
{...props}>{props.children}</View>), and ensure View is imported if not already;
locate the web branch check around Platform.OS === 'web' in
native-only-animated-view.tsx and compare it with the else branch that returns
Animated.View to keep prop parity.
In `@packages/ui/src/components/ui/progress.tsx`:
- Around line 12-24: The Progress component destructures value but never passes
it to ProgressPrimitive.Root, which breaks accessibility; update the Progress
function so ProgressPrimitive.Root receives the value prop (i.e., include value
in the props passed to ProgressPrimitive.Root) while keeping Indicator receiving
value as before; locate the Progress component and modify the JSX for
ProgressPrimitive.Root to forward the value prop (retain existing
className/props and indicatorClassName handling for Indicator).
In `@packages/ui/src/components/ui/switch.tsx`:
- Around line 16-29: The styling uses props.checked which only works for
controlled mode; update the conditional class logic on SwitchPrimitives.Thumb
(and the outer Switch container if present) to rely on the primitive's
data-state attribute selectors instead of props.checked—remove branches that
check props.checked and replace with data-state="checked"/data-state="unchecked"
based class toggles so the visual translate-x and dark:bg classes reflect the
actual DOM state for both controlled and uncontrolled (defaultChecked) usage.
In `@packages/ui/src/components/ui/tabs.tsx`:
- Around line 1-4: The file uses the type helper React.ComponentProps in several
places (e.g., in the TabsPrimitive-related prop types and exported components)
but never imports React for type references; add an explicit import for React at
the top of the file (e.g., import * as React from 'react') so all
React.ComponentProps type usages (in the
TabsRoot/TabsList/TabsTrigger/TabsContent prop types and any other locations)
resolve correctly without changing runtime behavior.
In `@packages/ui/src/components/ui/text.tsx`:
- Around line 55-56: The block mapping in the text variant assigns invalid ARIA
roles by casting 'blockquote' and 'code' to Role; update the mapping in the Text
component (the object containing blockquote and code entries) to stop assigning
ARIA roles for these variants — remove the Platform.select(... as Role) casts
and either omit the role entries or set them to undefined so the semantic HTML
elements render without an explicit role; ensure no other code in the Text
component (or related render/variant handlers) relies on those role values.
In `@packages/ui/src/components/ui/toggle.tsx`:
- Around line 50-57: The TextClassContext value currently includes the full root
className via the cn(...) call which leaks layout/container classes; update the
value passed to TextClassContext in the toggle component so it only contains
text-related classes (e.g., the 'text-sm native:text-lg text-foreground
font-medium' base classes plus the pressed-dependent color from
props.pressed/Platform.select) and remove the root props.className from that
cn(...) invocation; keep props.className applied to the root element only and
ensure references to value={...}, cn(...), props.pressed, and
Platform.select(...) are adjusted accordingly.
In `@packages/ui/src/lib/validation-errors.ts`:
- Around line 7-11: The current top-level-only extraction (variables first and
firstMessage) misses nested FieldErrors; replace it with a small recursive
extractor (e.g., findFirstErrorMessage(errors)) that walks objects/arrays in the
supplied errors structure, returns the first encountered .message string
(coercing to String) and use that value instead of firstMessage; update the
places that reference first/firstMessage to call this helper so nested
FieldErrorsImpl entries are discovered.
In `@tooling/typescript-config/base.json`:
- Line 20: Remove the global "node" typing from the base compilerOptions.types
in tooling/typescript-config/base.json so the base no longer forces Node types
on all packages; update the "types" array (currently ["nativewind/types",
"node"]) to omit "node", and add a note or document that packages which require
Node typings (e.g., the supabase package that uses process.env) should
explicitly opt-in by adding "node" to their own compilerOptions.types in their
local tsconfig (packages/ui, packages/features/auth, packages/features/account,
packages/supabase as needed).
---
Outside diff comments:
In `@packages/features/auth/src/password-reset/password-reset-form.tsx`:
- Around line 43-50: The success toast is shown immediately after calling
passwordResetMutation.mutate instead of waiting for the request to complete;
update the form.handleSubmit handler so that mutate is called with an options
object containing an onSuccess callback that calls toast.success('We sent you an
email to reset your password.'), and keep validation errors routed through
onValidationErrors as before; reference the passwordResetMutation.mutate call in
the password-reset-form component (which uses useRequestResetPassword), and
optionally add an onError callback to the mutate options to show toast.error on
failure.
---
Nitpick comments:
In `@packages/ui/src/components/ui/alert-dialog.tsx`:
- Around line 37-41: The JSX wraps children in an unnecessary fragment inside
NativeOnlyAnimatedView; update the render in the alert dialog component to pass
children directly to NativeOnlyAnimatedView (remove the <> {children} </>
wrapper) while keeping the existing entering and exiting props
(FadeIn.duration(200).delay(50) and FadeOut.duration(150)) so only
NativeOnlyAnimatedView, entering, exiting and children remain.
In `@packages/ui/src/components/ui/button.tsx`:
- Around line 95-97: The ButtonProps union incorrectly includes
React.RefAttributes<typeof Pressable>, which uses the component constructor type
and is redundant because React.ComponentProps<typeof Pressable> already includes
the ref in React 19; remove the React.RefAttributes<typeof Pressable> part so
ButtonProps becomes React.ComponentProps<typeof Pressable> & VariantProps<typeof
buttonVariants>, or if an explicit ref type is required use
React.RefAttributes<React.ComponentRef<typeof Pressable>> instead.
In `@packages/ui/src/components/ui/card.tsx`:
- Around line 24-50: The CardTitle/CardDescription prop types use incorrect
React.RefAttributes<typeof Text> and ref handling is inconsistent: remove the
explicit RefAttributes usage and rely on React.ComponentProps<typeof Text>
(which already includes ref in React 18+/19), and make both components forward
refs consistently by converting them to React.forwardRef wrappers (forward the
ref into the underlying Text element in both CardTitle and CardDescription);
update function signatures to use React.forwardRef and ensure you forward the
ref to the Text component (preserve className merging with cn and other props)
so both components have correct types and consistent ref behavior.
In `@packages/ui/src/components/ui/context-menu.tsx`:
- Around line 100-108: The code incorrectly asserts overlayStyle as typeof
StyleSheet.absoluteFill; remove that inaccurate assertion and ensure
overlayStyle is typed as a ViewStyle (or RegisteredStyle<ViewStyle>) instead.
Update the ContextMenu/overlay block that uses Platform.select,
StyleSheet.flatten and StyleSheet.absoluteFill so the flattened array is typed
like [StyleSheet.absoluteFill, overlayStyle as ViewStyle] (or cast overlayStyle
where it is defined to ViewStyle) rather than overlayStyle as typeof
StyleSheet.absoluteFill; alternatively import ViewStyle from react-native and
declare overlayStyle: ViewStyle | undefined to satisfy the type system.
In `@packages/ui/src/components/ui/dialog.tsx`:
- Around line 40-44: Replace the nested NativeOnlyAnimatedView pair with a
single NativeOnlyAnimatedView that merges the timing (use FadeIn with the 50ms
delay and 200ms duration and a single FadeOut.duration(150) for exiting) and
render {children} directly; update the component that uses
NativeOnlyAnimatedView (and references to FadeIn/FadeOut) to remove the extra
wrapper and redundant fragment so the animation timings are combined in one
view.
In `@packages/ui/src/components/ui/dropdown-menu.tsx`:
- Around line 106-114: The type assertion overlayStyle as typeof
StyleSheet.absoluteFill is incorrect; remove that assertion and pass
overlayStyle directly to StyleSheet.flatten (which accepts
StyleProp<ViewStyle>), so modify the Platform.select branch used in the
component where overlayStyle is passed (the StyleSheet.flatten call alongside
StyleSheet.absoluteFill) to call StyleSheet.flatten([StyleSheet.absoluteFill,
overlayStyle]) without the cast, ensuring overlayStyle retains its declared
StyleProp<ViewStyle> type.
In `@packages/ui/src/components/ui/menubar.tsx`:
- Around line 55-62: The Menubar is giving internal state precedence over the
controlled prop; change the component to prioritize the consumer's prop by using
valueProp before internal value (i.e., pass value={valueProp ?? value} to
MenubarPrimitive.Root), keep the existing onValueChange handler pattern
(onValueChange={onValueChangeProp ?? setValue}), and ensure the internal state
initializer for value reads from valueProp when present (initialize value state
from valueProp as the default) so controlled mode consistently wins and initial
render is correct; reference MenubarPrimitive.Root, valueProp, value,
onValueChangeProp, setValue, and closeMenu when making the edits.
In `@packages/ui/src/components/ui/tooltip.tsx`:
- Around line 29-35: The entering animation only distinguishes side === 'top' vs
other, causing left/right tooltips to use FadeInUp; update the
NativeOnlyAnimatedView entering logic (the entering prop where side is checked)
to handle 'left' and 'right' explicitly (e.g., map 'left' -> FadeInLeft, 'right'
-> FadeInRight, 'bottom' -> FadeInUp, 'top' -> FadeInDown) so mobile matches the
web behavior; modify the conditional/branch that currently uses FadeInDown and
FadeInUp to a small switch or lookup that returns the correct FadeIn* animation
for each side.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e28e008e-2161-42db-9c0f-c06cc2653f09
⛔ Files ignored due to path filters (2)
apps/expo-app/package-lock.jsonis excluded by!**/package-lock.jsonpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (72)
apps/expo-app/app.jsonapps/expo-app/app/(app)/(main)/_layout.tsxapps/expo-app/app/(app)/auth/error.tsxapps/expo-app/app/(app)/auth/password-reset.tsxapps/expo-app/app/(app)/auth/sign-in.tsxapps/expo-app/app/(app)/auth/sign-up.tsxapps/expo-app/babel.config.jsapps/expo-app/components/root-provider.tsxapps/expo-app/components/settings/settings-pages-list.tsxapps/expo-app/components/settings/theme-mode-toggle.tsxapps/expo-app/database.types.tsapps/expo-app/package.jsonapps/expo-app/supabase/seed.sqlpackage.jsonpackages/features/account/package.jsonpackages/features/account/src/components/update-email-form.tsxpackages/features/account/src/components/update-password-form.tsxpackages/features/account/src/components/update-profile-container.tsxpackages/features/account/src/components/update-profile-picture-form.tsxpackages/features/account/src/lib/hooks/use-fetch-profile.tspackages/features/account/src/lib/hooks/use-update-account-mutation.tspackages/features/account/src/lib/hooks/use-update-avatar.tspackages/features/account/src/lib/hooks/use-update-password.tspackages/features/auth/src/lib/schema.tspackages/features/auth/src/password-reset/password-reset-form.tsxpackages/features/auth/src/sign-in/sign-in-email-password.tsxpackages/features/auth/src/sign-up/sign-up-email-password.tsxpackages/supabase/package.jsonpackages/supabase/src/client.tspackages/supabase/src/database.types.tspackages/ui/components.jsonpackages/ui/package.jsonpackages/ui/src/components/ui/accordion.tsxpackages/ui/src/components/ui/alert-dialog.tsxpackages/ui/src/components/ui/alert.tsxpackages/ui/src/components/ui/aspect-ratio.tsxpackages/ui/src/components/ui/avatar.tsxpackages/ui/src/components/ui/badge.tsxpackages/ui/src/components/ui/button.tsxpackages/ui/src/components/ui/card.tsxpackages/ui/src/components/ui/checkbox.tsxpackages/ui/src/components/ui/context-menu.tsxpackages/ui/src/components/ui/dialog.tsxpackages/ui/src/components/ui/dropdown-menu.tsxpackages/ui/src/components/ui/hover-card.tsxpackages/ui/src/components/ui/icon.tsxpackages/ui/src/components/ui/input.tsxpackages/ui/src/components/ui/label.tsxpackages/ui/src/components/ui/menubar.tsxpackages/ui/src/components/ui/native-only-animated-view.tsxpackages/ui/src/components/ui/navigation-menu.tsxpackages/ui/src/components/ui/popover.tsxpackages/ui/src/components/ui/progress.tsxpackages/ui/src/components/ui/radio-group.tsxpackages/ui/src/components/ui/select.tsxpackages/ui/src/components/ui/separator.tsxpackages/ui/src/components/ui/switch.tsxpackages/ui/src/components/ui/table.tsxpackages/ui/src/components/ui/tabs.tsxpackages/ui/src/components/ui/text.tsxpackages/ui/src/components/ui/textarea.tsxpackages/ui/src/components/ui/toggle-group.tsxpackages/ui/src/components/ui/toggle.tsxpackages/ui/src/components/ui/tooltip.tsxpackages/ui/src/components/ui/typography.tsxpackages/ui/src/index.tspackages/ui/src/lib/validation-errors.tspnpm-workspace.yamltooling/eslint-config/base.jstooling/eslint-config/package.jsontooling/typescript-config/base.jsontooling/typescript-config/package.json
💤 Files with no reviewable changes (2)
- packages/ui/src/components/ui/typography.tsx
- tooling/eslint-config/package.json
| </Link> | ||
| </Button> | ||
| <ButtonLink variant="link" href="/auth/sign-in"> | ||
| <Text className="text-center">Already have an Account? Sign In.</Text> |
There was a problem hiding this comment.
Fix CTA capitalization for consistency.
Line 33 uses inconsistent title casing (Account, Sign In) compared with surrounding copy style.
Suggested copy tweak
- <Text className="text-center">Already have an Account? Sign In.</Text>
+ <Text className="text-center">Already have an account? Sign in.</Text>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Text className="text-center">Already have an Account? Sign In.</Text> | |
| <Text className="text-center">Already have an account? Sign in.</Text> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/expo-app/app/`(app)/auth/sign-up.tsx at line 33, Update the CTA string
in the Text element inside the sign-up component so it matches the surrounding
sentence-case copy style; replace "Already have an Account? Sign In." with
"Already have an account? Sign in." in the JSX Text node (the line rendering the
CTA in apps/expo-app/app/(app)/auth/sign-up.tsx).
| onValueChange={async (value) => { | ||
| if (!value) return; | ||
| setColorScheme(value as Theme); | ||
| await AsyncStorage.setItem('theme', value as Theme); | ||
| await AsyncStorage.setItem('theme', value); | ||
| }} |
There was a problem hiding this comment.
Handle AsyncStorage failures in the toggle handler.
Line 23 can reject and currently has no local handling, which risks unhandled promise rejections and silent preference persistence failure.
Suggested fix
onValueChange={async (value) => {
if (!value) return;
- setColorScheme(value as Theme);
- await AsyncStorage.setItem('theme', value);
+ setColorScheme(value as Theme);
+ try {
+ await AsyncStorage.setItem('theme', value);
+ } catch (error) {
+ console.error('Failed to persist theme preference', error);
+ }
}}As per coding guidelines, "Do not swallow errors; handle promises and async/await with proper error handling and non-sensitive context".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onValueChange={async (value) => { | |
| if (!value) return; | |
| setColorScheme(value as Theme); | |
| await AsyncStorage.setItem('theme', value as Theme); | |
| await AsyncStorage.setItem('theme', value); | |
| }} | |
| onValueChange={async (value) => { | |
| if (!value) return; | |
| setColorScheme(value as Theme); | |
| try { | |
| await AsyncStorage.setItem('theme', value); | |
| } catch (error) { | |
| console.error('Failed to persist theme preference', error); | |
| } | |
| }} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/expo-app/components/settings/theme-mode-toggle.tsx` around lines 20 -
24, The onValueChange handler for the theme toggle currently awaits
AsyncStorage.setItem without catching rejections; wrap the await call in a
try/catch inside the onValueChange async function (the handler that calls
setColorScheme and AsyncStorage.setItem) to handle storage failures, log a
non-sensitive error via your logger or console.error, and consider reverting or
ensuring UI/state consistency if setting storage fails (e.g., keep
setColorScheme but surface the error); do not swallow the error silently and
include identifiable context like the theme value and that the failure occurred
in AsyncStorage.setItem.
| if (previousPictureUrl) { | ||
| await deleteAvatarObject(client, previousPictureUrl); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C6 'previousPictureUrl|deleteAvatarObject|uploadAvatarBytes|update\(\{ picture_url' packages/features/account/src/lib/hooks/use-update-avatar.tsRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 2165
🏁 Script executed:
#!/bin/bash
# Get the complete file to see the full deleteAvatarObject function
cat -n packages/features/account/src/lib/hooks/use-update-avatar.ts | tail -20Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 732
Don't delete the current avatar before the replacement is committed.
Line 38 deletes the existing object before upload/update. If line 51 or lines 55-56 fail, the user loses their current avatar. Additionally, line 100 silently ignores errors from the storage remove() call; per the guidelines, errors must be handled comprehensively.
Proposed fix
- if (previousPictureUrl) {
- await deleteAvatarObject(client, previousPictureUrl);
- }
-
if (uri === null) {
+ if (previousPictureUrl) {
+ await deleteAvatarObject(client, previousPictureUrl);
+ }
await client
.from('accounts')
.update({ picture_url: null })
.eq('id', userId)
.throwOnError();
return null;
}
const publicUrl = await uploadAvatarBytes(client, uri, userId);
await client
.from('accounts')
.update({ picture_url: publicUrl })
.eq('id', userId)
.throwOnError();
+
+ if (previousPictureUrl) {
+ await deleteAvatarObject(client, previousPictureUrl);
+ }
return publicUrl;Also update deleteAvatarObject to handle errors:
async function deleteAvatarObject(
client: SupabaseClient<Database>,
url: string,
) {
// Strip the cache-buster query string to recover the storage key.
const fileName = url.split('/').pop()?.split('?')[0];
if (!fileName) return;
- await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+ const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+ if (error) throw error;
}Also applies to: 51-59, 100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/features/account/src/lib/hooks/use-update-avatar.ts` around lines 38
- 40, The current flow deletes the previous avatar via deleteAvatarObject before
the new upload/update is committed, and storage.remove() errors are silently
ignored; change the flow in use-update-avatar (the hook) to first upload the new
avatar and commit the user/profile update referencing the new URL, then only
after a successful commit call deleteAvatarObject for the old URL so the user
never loses the current avatar if upload/update fails, and modify
deleteAvatarObject (and any direct storage.remove() call) to catch and handle
errors (log and surface/return them instead of swallowing) so storage.remove
failures are handled comprehensively.
| const fileName = url.split('/').pop()?.split('?')[0]; | ||
| if (!fileName) return; | ||
|
|
||
| await client.storage.from(AVATARS_BUCKET).remove([fileName]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C3 'remove\(\[fileName\]\)' packages/features/account/src/lib/hooks/use-update-avatar.tsRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 250
🏁 Script executed:
cat -n packages/features/account/src/lib/hooks/use-update-avatar.tsRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 3828
Handle storage-delete errors instead of swallowing them.
Line 100 ignores remove(...) errors, which can silently leave old avatar files behind. This violates the error-handling requirement in the coding guidelines. The codebase already demonstrates the correct pattern in uploadAvatarBytes (lines 82–87), which properly destructures and throws storage errors.
Proposed fix
- await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+ const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+ if (error) throw error;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await client.storage.from(AVATARS_BUCKET).remove([fileName]); | |
| const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]); | |
| if (error) throw error; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/features/account/src/lib/hooks/use-update-avatar.ts` at line 100,
The remove(...) call is currently ignoring storage errors; change it to follow
the same pattern as uploadAvatarBytes by awaiting
client.storage.from(AVATARS_BUCKET).remove([fileName]) and destructuring the
result into { data, error } (or similar), then throw or propagate the error if
present so deletions aren't silently swallowed; update the remove call inside
useUpdateAvatar (referencing AVATARS_BUCKET and the client.storage.remove
invocation) to handle and surface any storage errors.
| import { Icon } from './icon'; | ||
| import { TextClassContext } from './text'; | ||
| import { cn } from '../../lib/utils'; | ||
| import * as AccordionPrimitive from '@rn-primitives/accordion'; | ||
| import { ChevronDown } from 'lucide-react-native'; | ||
| import { Platform, Pressable, View } from 'react-native'; | ||
| import Animated, { | ||
| Extrapolation, | ||
| FadeIn, | ||
| FadeOutUp, | ||
| LayoutAnimationConfig, | ||
| LinearTransition, | ||
| interpolate, | ||
| useAnimatedStyle, | ||
| useDerivedValue, | ||
| withTiming, | ||
| } from 'react-native-reanimated'; |
There was a problem hiding this comment.
Missing React import for type usage.
React.ComponentProps is used at lines 19, 36, 62, and 119, but React is not imported.
Proposed fix
+import * as React from 'react';
import { Icon } from './icon';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { Icon } from './icon'; | |
| import { TextClassContext } from './text'; | |
| import { cn } from '../../lib/utils'; | |
| import * as AccordionPrimitive from '@rn-primitives/accordion'; | |
| import { ChevronDown } from 'lucide-react-native'; | |
| import { Platform, Pressable, View } from 'react-native'; | |
| import Animated, { | |
| Extrapolation, | |
| FadeIn, | |
| FadeOutUp, | |
| LayoutAnimationConfig, | |
| LinearTransition, | |
| interpolate, | |
| useAnimatedStyle, | |
| useDerivedValue, | |
| withTiming, | |
| } from 'react-native-reanimated'; | |
| import * as React from 'react'; | |
| import { Icon } from './icon'; | |
| import { TextClassContext } from './text'; | |
| import { cn } from '../../lib/utils'; | |
| import * as AccordionPrimitive from '`@rn-primitives/accordion`'; | |
| import { ChevronDown } from 'lucide-react-native'; | |
| import { Platform, Pressable, View } from 'react-native'; | |
| import Animated, { | |
| FadeOutUp, | |
| LayoutAnimationConfig, | |
| LinearTransition, | |
| useAnimatedStyle, | |
| useDerivedValue, | |
| withTiming, | |
| } from 'react-native-reanimated'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/ui/accordion.tsx` around lines 1 - 14, The file
uses React types via React.ComponentProps (seen in the props for components such
as the AccordionTrigger, AccordionItem, AccordionContent, and others where
React.ComponentProps is referenced) but never imports React; add an import for
React (e.g., import * as React from 'react' or import React from 'react') at the
top of the file so the type references resolve, keeping the existing imports
intact and without changing runtime behavior.
| import { TextClassContext } from './text'; | ||
| import { cn } from '../../lib/utils'; | ||
| import * as TabsPrimitive from '@rn-primitives/tabs'; | ||
| import { Platform } from 'react-native'; |
There was a problem hiding this comment.
Missing React import for type usage.
React.ComponentProps is used throughout the file (lines 9, 16, 32, 60), but React is not imported. While the JSX transform auto-injects React for JSX, the type reference React.ComponentProps requires an explicit import.
Proposed fix
+import * as React from 'react';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as TabsPrimitive from '`@rn-primitives/tabs`';
import { Platform } from 'react-native';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { TextClassContext } from './text'; | |
| import { cn } from '../../lib/utils'; | |
| import * as TabsPrimitive from '@rn-primitives/tabs'; | |
| import { Platform } from 'react-native'; | |
| import * as React from 'react'; | |
| import { TextClassContext } from './text'; | |
| import { cn } from '../../lib/utils'; | |
| import * as TabsPrimitive from '`@rn-primitives/tabs`'; | |
| import { Platform } from 'react-native'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/ui/tabs.tsx` around lines 1 - 4, The file uses the
type helper React.ComponentProps in several places (e.g., in the
TabsPrimitive-related prop types and exported components) but never imports
React for type references; add an explicit import for React at the top of the
file (e.g., import * as React from 'react') so all React.ComponentProps type
usages (in the TabsRoot/TabsList/TabsTrigger/TabsContent prop types and any
other locations) resolve correctly without changing runtime behavior.
| blockquote: Platform.select({ web: 'blockquote' as Role }), | ||
| code: Platform.select({ web: 'code' as Role }), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "text.tsx" -path "*/packages/ui/*" 2>/dev/null | head -5Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 67
🏁 Script executed:
git ls-files packages/ui/src/components/ui/ | grep -E "\.(ts|tsx)$"Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 1688
🏁 Script executed:
cat -n packages/ui/src/components/ui/text.tsx | head -100Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 4142
🏁 Script executed:
rg "type Role|export.*Role" packages/ui/src/ --type ts --type tsxRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 113
🌐 Web query:
ARIA valid roles specification 2024 blockquote code roles
💡 Result:
In ARIA, the valid/appropriate role value for an HTML
isCitations:
- 1: https://www.w3.org/TR/aria-in-html/
- 2: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/blockquote
- 3: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
- 4: https://w3c.github.io/html-reference/aria/blockquote.html
- 5: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/structural_roles
🏁 Script executed:
rg "blockquote|code" packages/ui/src/components/ui/text.tsx -A 2 -B 2Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 1049
🏁 Script executed:
rg "Platform.select" packages/ui/src/components/ui/text.tsx -A 1 -B 1Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 816
🏁 Script executed:
# Check if there are type stubs or node_modules references for react-native Role find . -name "*.d.ts" -o -name "*.ts" | xargs grep -l "type Role" | head -10Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 67
🏁 Script executed:
# Look for how other components handle optional roles on mobile vs web rg "Platform.select.*Role" packages/ui/src/ -A 1 -B 1Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 373
🌐 Web query:
ARIA valid roles list W3C specification 2024💡 Result:
As of May 2026, the W3C WAI-ARIA 1.2 specification remains the current W3C Recommendation [1][2]. While WAI-ARIA 1.3 is under active development as a draft [3][1], it has not yet reached the status of a final W3C Recommendation. The authoritative list of valid ARIA roles is defined in the WAI-ARIA specification [4][5]. These roles are categorized into several types, including landmark, widget, document structure, and abstract roles [6][1]. Key points regarding ARIA roles: 1. Current Standard: WAI-ARIA 1.2 is the stable, recommended version [1][2]. 2. Development Status: WAI-ARIA 1.3 is in development [3][1]. It introduces proposed new roles such as suggestion, comment, and mark [3][1]. Because these are part of a draft, they should be treated as experimental until the specification is finalized [1]. 3. Abstract Roles: The specification defines abstract roles (e.g., widget, structure, input) which are used for the ontology of the specification itself [6][1]. Authors must not use abstract roles in their content [6]. 4. Usage: Authors should use the role attribute to assign these roles to HTML elements [6][7]. However, it is generally recommended to use native HTML elements whenever possible, as they often have implicit ARIA semantics that should not be overridden [8][9]. For the most accurate and up-to-date list of roles, you should consult the official W3C WAI-ARIA specification documents [4][5]. The MDN Web Docs also provide a comprehensive and developer-friendly reference for these roles [6].
Citations:
- 1: https://www.levelaccess.com/blog/wai-aria-explained-a-guide-to-accessible-rich-internet-applications/
- 2: https://w3.org/TR/2023/REC-wai-aria-1.2-20230606
- 3: https://www.w3.org/WAI/standards-guidelines/aria/
- 4: https://w3.org/TR/wai-aria
- 5: https://www.w3.org/TR/wai-aria-1.3/
- 6: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles
- 7: https://w3c.github.io/aria/
- 8: http://www.w3.org./TR/aria-in-html/
- 9: https://www.w3.org/TR/2025/REC-html-aria-20250123/
Remove invalid ARIA role assignments for blockquote and code variants.
blockquote and code are not valid ARIA role values per the W3C WAI-ARIA 1.2 specification. These are semantic HTML elements that should not have explicit role attributes. Casting them to Role on lines 55–56 bypasses TypeScript type safety and produces invalid accessibility markup.
Proposed fix
const ROLE: Partial<Record<TextVariant, Role>> = {
h1: 'heading',
h2: 'heading',
h3: 'heading',
h4: 'heading',
- blockquote: Platform.select({ web: 'blockquote' as Role }),
- code: Platform.select({ web: 'code' as Role }),
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/ui/text.tsx` around lines 55 - 56, The block
mapping in the text variant assigns invalid ARIA roles by casting 'blockquote'
and 'code' to Role; update the mapping in the Text component (the object
containing blockquote and code entries) to stop assigning ARIA roles for these
variants — remove the Platform.select(... as Role) casts and either omit the
role entries or set them to undefined so the semantic HTML elements render
without an explicit role; ensure no other code in the Text component (or related
render/variant handlers) relies on those role values.
| value={cn( | ||
| // Kit extension: native:text-lg | ||
| 'text-sm native:text-lg text-foreground font-medium', | ||
| props.pressed | ||
| ? 'text-accent-foreground' | ||
| : Platform.select({ web: 'group-hover:text-muted-foreground' }), | ||
| className | ||
| )}> |
There was a problem hiding this comment.
Avoid passing root className into TextClassContext.
This leaks layout/container classes into nested text/icon styling. Keep context value text-only.
Suggested fix
<TextClassContext.Provider
value={cn(
// Kit extension: native:text-lg
'text-sm native:text-lg text-foreground font-medium',
props.pressed
? 'text-accent-foreground'
- : Platform.select({ web: 'group-hover:text-muted-foreground' }),
- className
+ : Platform.select({ web: 'group-hover:text-muted-foreground' })
)}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| value={cn( | |
| // Kit extension: native:text-lg | |
| 'text-sm native:text-lg text-foreground font-medium', | |
| props.pressed | |
| ? 'text-accent-foreground' | |
| : Platform.select({ web: 'group-hover:text-muted-foreground' }), | |
| className | |
| )}> | |
| value={cn( | |
| // Kit extension: native:text-lg | |
| 'text-sm native:text-lg text-foreground font-medium', | |
| props.pressed | |
| ? 'text-accent-foreground' | |
| : Platform.select({ web: 'group-hover:text-muted-foreground' }) | |
| )}> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/ui/toggle.tsx` around lines 50 - 57, The
TextClassContext value currently includes the full root className via the
cn(...) call which leaks layout/container classes; update the value passed to
TextClassContext in the toggle component so it only contains text-related
classes (e.g., the 'text-sm native:text-lg text-foreground font-medium' base
classes plus the pressed-dependent color from props.pressed/Platform.select) and
remove the root props.className from that cn(...) invocation; keep
props.className applied to the root element only and ensure references to
value={...}, cn(...), props.pressed, and Platform.select(...) are adjusted
accordingly.
| const first = Object.values(errors).find( | ||
| (e) => e && 'message' in e && e.message, | ||
| ); | ||
| const firstMessage = | ||
| first && 'message' in first && first.message ? String(first.message) : ''; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/ui/src/lib/validation-errors.tsRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 624
🏁 Script executed:
rg "FieldErrors" --type ts --type tsx -A 3 -B 3Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 113
🏁 Script executed:
fd -e ts -e tsx | xargs rg "from 'react-hook-form'" | head -20Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 897
🏁 Script executed:
rg "onValidationErrors" -A 5 -B 2Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 9429
🏁 Script executed:
rg "FieldErrors" -A 2 -B 2 --type tsRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 699
🏁 Script executed:
web_search
React Hook Form FieldErrors nested structureRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 180
🌐 Web query:
React Hook Form FieldErrors nested structure type definition
💡 Result:
In React Hook Form, the FieldErrors type represents the structure of form validation errors, which mirrors the shape of your form data [1]. Because form data can be deeply nested, FieldErrors uses a recursive type definition to maintain this hierarchy [1]. The core type definition is FieldErrors, which maps your form values (T) to an error object [1]. For nested objects, it utilizes FieldErrorsImpl, which recursively traverses the object structure [1]. Key aspects of the FieldErrors structure include: 1. Recursive Mapping: For any property in your form data that is an object, FieldErrors will contain a corresponding nested object [1]. 2. FieldError Type: At the leaf nodes (the actual fields), the type is FieldError [1]. This type contains details about the validation failure, such as: - type: The validation rule that failed (e.g., 'required', 'min') [1][2]. - message: The error message string [1][2]. - ref: A reference to the input element [1][2]. - types: An object containing multiple validation errors if multiple rules failed for the same field [1][2]. 3. Handling Nested Objects: When a field is an object, the corresponding entry in the errors object is a combination of FieldError (if the object itself has an error) and the nested FieldErrorsImpl for its children [1]. The official type definition from the React Hook Form source code is as follows [1]: type FieldErrorsImpl = { [K in keyof T]?: T[K] extends BrowserNativeObject | Blob? FieldError: K extends 'root' | root.${string}? GlobalError: T[K] extends object? Merge<FieldError, FieldErrorsImpl<T[K]>>: FieldError; }; This structure ensures that you can access nested errors using standard dot notation (e.g., errors.user?.address?.city) [3][4]. If you encounter issues where the TypeScript type does not seem to match your nested structure, it is often due to the complexity of recursive types in TypeScript; developers sometimes use type casting or utility types like DeepPartial to work around specific edge cases [5][1].
Citations:
- 1: https://github.com/react-hook-form/react-hook-form/blob/master/src/types/errors.ts
- 2: https://react-hook-form.com/ts
- 3: https://stackoverflow.com/questions/71301798/registering-nested-objects-with-react-hook-form
- 4:
errorsfor nested fields not in expected format react-hook-form/react-hook-form#515 - 5: Type issue: FieldError does not cover nested objects from formState.errors react-hook-form/react-hook-form#10601
🏁 Script executed:
rg "useForm\|FormState\|FieldArray" packages/features -A 10 | head -100Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 67
🏁 Script executed:
# Check if any forms use nested field structures
rg "register.*\." packages/features -A 2 | head -50Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 67
Add recursive error extraction to handle nested field errors.
The current implementation at lines 7–11 only checks top-level error entries. React Hook Form's FieldErrors type is recursive and supports nested objects (via FieldErrorsImpl<T>), so deeply nested field errors will be missed, causing silent toast failures.
Proposed fix
+function findFirstMessage(value: unknown): string | undefined {
+ if (!value || typeof value !== 'object') return undefined;
+ if ('message' in value && value.message) return String(value.message);
+ for (const child of Object.values(value as Record<string, unknown>)) {
+ const found = findFirstMessage(child);
+ if (found) return found;
+ }
+ return undefined;
+}
+
export function onValidationErrors(
handler: (firstMessage: string, errors: FieldErrors) => void,
) {
return (errors: FieldErrors) => {
- const first = Object.values(errors).find(
- (e) => e && 'message' in e && e.message,
- );
- const firstMessage =
- first && 'message' in first && first.message ? String(first.message) : '';
+ const firstMessage = findFirstMessage(errors) ?? '';
handler(firstMessage, errors);
};
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/lib/validation-errors.ts` around lines 7 - 11, The current
top-level-only extraction (variables first and firstMessage) misses nested
FieldErrors; replace it with a small recursive extractor (e.g.,
findFirstErrorMessage(errors)) that walks objects/arrays in the supplied errors
structure, returns the first encountered .message string (coercing to String)
and use that value instead of firstMessage; update the places that reference
first/firstMessage to call this helper so nested FieldErrorsImpl entries are
discovered.
| "incremental": true, | ||
| "noUncheckedIndexedAccess": true, | ||
| "types": ["nativewind/types"] | ||
| "types": ["nativewind/types", "node"] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== tsconfig files extending `@kit/typescript-config/base` =="
rg -n --glob '**/tsconfig*.json' '"extends"\s*:\s*".*typescript-config.*/base(\.json)?"'
echo
echo "== tsconfig files explicitly adding compilerOptions.types =="
rg -n --glob '**/tsconfig*.json' '"types"\s*:'
echo
echo "== Potential Node-global usage in Expo app sources (sanity check) =="
rg -n --glob 'apps/expo-app/**' '\b(process|Buffer|global|__dirname|__filename)\b' || trueRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 716
🏁 Script executed:
cat -n tooling/typescript-config/base.jsonRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 878
🏁 Script executed:
cat -n apps/expo-app/tsconfig.jsonRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 691
🏁 Script executed:
# Check if any of the inheriting packages are client/native targets
for file in packages/ui/tsconfig.json packages/features/auth/tsconfig.json packages/features/account/tsconfig.json packages/supabase/tsconfig.json; do
echo "=== $file ==="
rg -A 5 -B 2 '"compilerOptions"' "$file" | head -20
doneRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 901
🏁 Script executed:
# Check for Node-specific API usage in the shared packages that inherit from base
for package_dir in packages/ui packages/features/auth packages/features/account packages/supabase; do
echo "=== Checking $package_dir for Node APIs ==="
rg -l '\b(process|Buffer|fs\.|path\.|require\(|__dirname|__filename)\b' "$package_dir/src" 2>/dev/null || echo " No Node APIs found"
doneRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 383
🏁 Script executed:
# Check package.json files to understand what these packages are
for package_dir in packages/ui packages/features/auth packages/features/account packages/supabase; do
if [ -f "$package_dir/package.json" ]; then
echo "=== $package_dir/package.json (key fields) ==="
rg -A 3 '"(name|description|main|exports)"' "$package_dir/package.json" | head -15
fi
doneRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 1145
🏁 Script executed:
# Check if these packages are explicitly isomorphic or have entry points
rg -l 'browser|client|react-native' packages/*/package.json packages/features/*/package.jsonRepository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 141
🏁 Script executed:
cat -n packages/supabase/src/client.ts | head -50Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 1568
🏁 Script executed:
# Check if supabase package has separate server/client exports
cat -n packages/supabase/package.json | head -30Repository: makerkit/react-native-expo-turbo-saas-kit
Length of output: 1176
Remove global Node typings from shared base config.
The base config at line 20 includes "node" types by default for all packages that inherit from it. This is unnecessary—while the supabase package uses process.env, it already declares @types/node as a devDependency, indicating types should be scoped locally. Shared packages (ui, auth, account, supabase) should opt-in to Node types explicitly rather than inheriting them through the base config.
Proposed fix
- "types": ["nativewind/types", "node"]
+ "types": ["nativewind/types"]Projects inheriting this base—packages/ui, packages/features/auth, packages/features/account, and packages/supabase—should add "node" to their compilerOptions.types locally if needed.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "types": ["nativewind/types", "node"] | |
| "types": ["nativewind/types"] |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tooling/typescript-config/base.json` at line 20, Remove the global "node"
typing from the base compilerOptions.types in
tooling/typescript-config/base.json so the base no longer forces Node types on
all packages; update the "types" array (currently ["nativewind/types", "node"])
to omit "node", and add a note or document that packages which require Node
typings (e.g., the supabase package that uses process.env) should explicitly
opt-in by adding "node" to their own compilerOptions.types in their local
tsconfig (packages/ui, packages/features/auth, packages/features/account,
packages/supabase as needed).
Changes
Upgrades
react-native-workletspeer), explicit@expo/metro-runtimepeericon,aspect-ratio,context-menu,tooltip,native-only-animated-view). Kit-extensions retained where they suit the kit better (centered-card Alert,mobile-first
native:*sizing on interactive components, gap-based ToggleGroup)@supabase/*,@tanstack/react-query,@rn-primitives/*,tailwind-merge,sonner-native, etc.). Policy: mirror Expo'sbundledNativeModules.jsonfor Expo-managed deps, caret-range everythingelse
Reworked flows
expo-document-picker(web-only APIs) →expo-image-picker+expo-image-manipulator(resize 512px, JPEG q=0.8). Single hook handles upload + clearonValidationErrorshelper in@kit/uisurfaces the first zod error as a toast across all 6 RHF forms (was previously silent). Friendlier zod messages on auth + account schemasBug fixes
Iconwrapper{...field}spread was eating keystrokes on RNTextInput— switched to explicitvalue/onBlur/onChangeTextlocalhost→10.0.2.2) so local Supabase is reachable from the emulatorCleanup
@vercel/style-guide,sonner(web;sonner-nativecovers web too),expo-document-picker, staleapps/expo-app/package-lock.jsontypography.tsxremoved;H1–Mutednow shorthand exports ontext.tsx(no caller changes)Notes for update
as before — no intentional breaking changes for kit consumers.
expo-image-picker,expo-image-manipulator,expo-crypto) — consumers needprebuild --cleanafter upgradingTested
pnpm expo:doctorpassesSummary by CodeRabbit
Release Notes
New Features
Bug Fixes
Improvements